package com.gmail.volodymyrdotsenko.rooaddon.simpleweb;

import static org.springframework.roo.classpath.customdata.CustomDataKeys.PERSISTENT_TYPE;
import static org.springframework.roo.model.RooJavaType.ROO_WEB_SCAFFOLD;
import static org.springframework.roo.model.SpringJavaType.CONTROLLER;
import static org.springframework.roo.model.SpringJavaType.REQUEST_MAPPING;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.BooleanAttributeValue;
import org.springframework.roo.classpath.details.annotations.ClassAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.project.Dependency;
import org.springframework.roo.project.DependencyScope;
import org.springframework.roo.project.DependencyType;
import org.springframework.roo.project.Repository;
import org.springframework.roo.support.logging.HandlerUtils;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Element;

/**
 * Implementation of operations this add-on offers.
 * 
 * @since 1.1
 */
@Component
// Use these Apache Felix annotations to register your commands class in the Roo
// container
@Service
public class SimplewebOperationsImpl implements SimplewebOperations {

	/**
	 * Use ProjectOperations to install new dependencies, plugins, properties,
	 * etc into the project configuration
	 */
	@Reference
	private ProjectOperations projectOperations;

	/**
	 * Use TypeLocationService to find types which are annotated with a given
	 * annotation in the project
	 */
	@Reference
	private TypeLocationService typeLocationService;

	/**
	 * Use TypeManagementService to change types
	 */
	@Reference
	private TypeManagementService typeManagementService;

	private static final Logger LOGGER = HandlerUtils
			.getLogger(SimplewebOperationsImpl.class);

	private static final JavaSymbolName PATH = new JavaSymbolName("path");
	private static final JavaSymbolName VALUE = new JavaSymbolName("value");

	@Reference
	private MetadataDependencyRegistry dependencyRegistry;
	@Reference
	private MetadataService metadataService;
	@Reference
	private PathResolver pathResolver;

	// @Reference
	// private WebMvcOperations webMvcOperations;

	/** {@inheritDoc} */
	@Override
	public boolean isCommandAvailable() {
		// Check if a project has been created
		// return projectOperations.isFocusedProjectAvailable();
		return true;
	}

	@Override
	public void createAutomaticController(JavaType controller, JavaType entity,
			Set<String> disallowedOperations, String path) {

		Validate.notNull(controller, "Controller Java Type required");
		Validate.notNull(entity, "Entity Java Type required");
		Validate.notNull(disallowedOperations,
				"Set of disallowed operations required");
		Validate.notBlank(path, "Controller base path required");

		// Look for an existing controller mapped to this path
		final ClassOrInterfaceTypeDetails existingController = getExistingController(path);

		// webMvcOperations.installConversionService(controller.getPackage());

		List<AnnotationMetadataBuilder> annotations = null;

		ClassOrInterfaceTypeDetailsBuilder cidBuilder = null;
		if (existingController == null) {
			final LogicalPath controllerPath = pathResolver
					.getFocusedPath(Path.SRC_MAIN_JAVA);
			final String resourceIdentifier = typeLocationService
					.getPhysicalTypeCanonicalPath(controller, controllerPath);
			final String declaredByMetadataId = PhysicalTypeIdentifier
					.createIdentifier(controller,
							pathResolver.getPath(resourceIdentifier));

			// Create annotation @RequestMapping("/myobject/**")
			final List<AnnotationAttributeValue<?>> requestMappingAttributes = new ArrayList<AnnotationAttributeValue<?>>();
			requestMappingAttributes.add(new StringAttributeValue(VALUE, "/"
					+ path));
			annotations = new ArrayList<AnnotationMetadataBuilder>();
			annotations.add(new AnnotationMetadataBuilder(REQUEST_MAPPING,
					requestMappingAttributes));

			// Create annotation @Controller
			final List<AnnotationAttributeValue<?>> controllerAttributes = new ArrayList<AnnotationAttributeValue<?>>();
			annotations.add(new AnnotationMetadataBuilder(CONTROLLER,
					controllerAttributes));

			// Create annotation @RooWebScaffold(path = "/test",
			// formBackingObject = MyObject.class)
			annotations.add(getRooWebScaffoldAnnotation(entity,
					disallowedOperations, path, PATH));
			cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(
					declaredByMetadataId, Modifier.PUBLIC, controller,
					PhysicalTypeCategory.CLASS);
		} else {
			cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(
					existingController);
			annotations = cidBuilder.getAnnotations();
			if (MemberFindingUtils.getAnnotationOfType(
					existingController.getAnnotations(), ROO_WEB_SCAFFOLD) == null) {
				annotations.add(getRooWebScaffoldAnnotation(entity,
						disallowedOperations, path, PATH));
			}
		}
		cidBuilder.setAnnotations(annotations);
		typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());

	}

	/**
	 * Looks for an existing controller mapped to the given path
	 * 
	 * @param path
	 *            (required)
	 * @return <code>null</code> if there is no such controller
	 */
	private ClassOrInterfaceTypeDetails getExistingController(final String path) {
		for (final ClassOrInterfaceTypeDetails cid : typeLocationService
				.findClassesOrInterfaceDetailsWithAnnotation(REQUEST_MAPPING)) {
			final AnnotationAttributeValue<?> attribute = MemberFindingUtils
					.getAnnotationOfType(cid.getAnnotations(), REQUEST_MAPPING)
					.getAttribute(VALUE);
			if (attribute instanceof ArrayAttributeValue) {
				final ArrayAttributeValue<?> mappingAttribute = (ArrayAttributeValue<?>) attribute;
				if (mappingAttribute.getValue().size() > 1) {
					LOGGER.warning("Skipping controller '"
							+ cid.getName().getFullyQualifiedTypeName()
							+ "' as it contains more than one path");
					continue;
				} else if (mappingAttribute.getValue().size() == 1) {
					final StringAttributeValue attr = (StringAttributeValue) mappingAttribute
							.getValue().get(0);
					final String mapping = attr.getValue();
					if (StringUtils.isNotBlank(mapping)
							&& mapping.equalsIgnoreCase("/" + path)) {
						return cid;
					}
				}
			} else if (attribute instanceof StringAttributeValue) {
				final StringAttributeValue mappingAttribute = (StringAttributeValue) attribute;
				if (mappingAttribute != null) {
					final String mapping = mappingAttribute.getValue();
					if (StringUtils.isNotBlank(mapping)
							&& mapping.equalsIgnoreCase("/" + path)) {
						return cid;
					}
				}
			}
		}
		return null;
	}

	private AnnotationMetadataBuilder getRooWebScaffoldAnnotation(
			final JavaType entity, final Set<String> disallowedOperations,
			final String path, final JavaSymbolName pathName) {

		final List<AnnotationAttributeValue<?>> rooWebScaffoldAttributes = new ArrayList<AnnotationAttributeValue<?>>();
		rooWebScaffoldAttributes.add(new StringAttributeValue(pathName, path));
		rooWebScaffoldAttributes.add(new ClassAttributeValue(
				new JavaSymbolName("formBackingObject"), entity));
		for (final String operation : disallowedOperations) {
			rooWebScaffoldAttributes.add(new BooleanAttributeValue(
					new JavaSymbolName(operation), false));
		}
		return new AnnotationMetadataBuilder(ROO_WEB_SCAFFOLD,
				rooWebScaffoldAttributes);
	}
}